module UnitComplexity

import IO;
import List;
import String;
import Volume;
import UnitSize;
import lang::java::jdt::JavaADT;
import lang::java::jdt::Java;
import lang::java::jdt::JDT;
import util::Math;

/*
In general,

    add 1 for each if statement.
    add 1 for each for statement.
    add 1 for each while loop
    add 1 for each do-while loop.
    add 1 for each && (an implied if statement).
    add 1 for each || (an implied if statement).
    add 1 for each ? (an implied if statement).
    add 1 for each . (an implied if statement).
    add 1 for each case statement.
    add 1 for each default statement.
    add 1 for each catch statement.
    add 1 for each finaly statement.
    add 1 for each continue statement.
*/


public int unitComplexity(AstNode unit)
{
	if(methodDeclaration(_,_,_,_,_,_,_) := unit)
	{
		return 0;
	}
	
	int count = 1;
	//iprintln(unit);
	//iprintln("-------------------------------------");
	visit(unit)
	{
		case ifStatement(_,_,_) : 			count += 1;
		case forStatement(_,_,_,_) : 		count += 1;
		case enhancedForStatement(_,_) : 	count += 1;
		case whileStatement(_,_) : 			count += 1;
		case doStatement(_,_) : 			count += 1;
		case switchCase(_,_) : 				count += 1;
		case catchClause(_,_) : 			count += 1;
		case continueStatement(_) :			count += 1;
	}
	return count;
}

public real getUnitComplexityClass(list[loc] locs)
{
	str ranking = getRanking(locs);
	
	switch(ranking) {
		case "--" : return 1.0;
		case "-"  : return 0.8;
		case "0"  : return 0.6;
		case "+"  : return 0.4;
		case "++" : return 0.2;
		default: return 0.0;
	}
}

public str getRanking()
{
	return getRanking([]);
}

public str getRanking(loc file)
{
	return getRanking([file]);
}

public str getRanking(list[loc] files)
{
	AstNodeRel methods = {};
	
	if(files == [])
	{
		fm = extractProject(|project://SmallSql|);
		//fm = extractProject(|project://hsqldb|);
		methods = fm@methodBodies;
	}
	else
	{
		for(loc file <- files)
		{
			fm = extractClass(file);
			//fm = extractClass(|project://SmallSql/src/smallsql/database/Logger.java|);
			methods += fm@methodBodies;
		}
	}
	
	//iprintln(methods);
	
	list[tuple[int,int]] counts = [];
	for (<e,ast> <- methods)
	{
		loc l = ast@location;
		//iprintln(l);
		counts += <unitSize(l),unitComplexity(ast)>;
		
	}
	
	//iprintln(counts);
	
	compl = ("without" : 0, "moderate" : 0, "high" : 0, "very high" : 0, "all" : 0);
	
	for (<s,c> <- counts)
	{
		switch(getRisk(c))	{	case "without":   { compl["without"]   += s; compl["all"] += s; }
								case "moderate":  { compl["moderate"]  += s; compl["all"] += s; }
								case "high":      { compl["high"]      += s; compl["all"] += s; }
								case "very high": { compl["very high"] += s; compl["all"] += s; }
							};
	}
	
	real without,moderate,high,veryhigh;
	//No functions so no complexity;
	if(compl["all"] == 0)
	{
		return "++";		
	}
	without  = toReal(compl["without"])   / toReal(compl["all"]);
 	moderate = toReal(compl["moderate"])  / toReal(compl["all"]);
 	high     = toReal(compl["high"])      / toReal(compl["all"]);
 	veryhigh = toReal(compl["very high"]) / toReal(compl["all"]);
 	
 	//real average = toReal(mp["all"]) / toReal(size(counts));
 	//println("average:   " + toString(average));
 	
 	//println("without:   " + toString(without));
 	//println("moderate:  " + toString(moderate));
 	//println("high:      " + toString(high));
 	//println("very high: " + toString(veryhigh));
 	//
 	if (moderate <= 0.25 && high <= 0.0 && veryhigh <= 0.0)
 		return "++";
 	else if (moderate <= 0.30 && high <= 0.05 && veryhigh <= 0.0)
 		return "+";
 	else if (moderate <= 0.40 && high <= 0.10 && veryhigh <= 0.0)
 		return "o";
 	else if (moderate < 0.50 && high < 0.15 && veryhigh < 0.05)
 		return "-";
 	else
 		return "--";
}

// Convert complexity to risk factor.
public str getRisk(int complexity)
{
	if(complexity <= 10) 
		return "without";
	else if(complexity <= 20)
		return "moderate"; 
	else if(complexity <= 50)
		return "high";
	else
		return "very high";
}